
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: FieldType.java 380 2010-03-11 22:27:58Z archie.cobbs $
 */

package org.dellroad.sidekar;

import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dellroad.sidekar.schema.model.ArrayFieldModel;
import org.dellroad.sidekar.schema.model.BigIntegerFieldModel;
import org.dellroad.sidekar.schema.model.BooleanFieldModel;
import org.dellroad.sidekar.schema.model.ByteFieldModel;
import org.dellroad.sidekar.schema.model.CharacterFieldModel;
import org.dellroad.sidekar.schema.model.DateFieldModel;
import org.dellroad.sidekar.schema.model.DoubleFieldModel;
import org.dellroad.sidekar.schema.model.FieldModel;
import org.dellroad.sidekar.schema.model.FloatFieldModel;
import org.dellroad.sidekar.schema.model.IntegerFieldModel;
import org.dellroad.sidekar.schema.model.ListFieldModel;
import org.dellroad.sidekar.schema.model.LongFieldModel;
import org.dellroad.sidekar.schema.model.MapFieldModel;
import org.dellroad.sidekar.schema.model.ReferenceFieldModel;
import org.dellroad.sidekar.schema.model.SetFieldModel;
import org.dellroad.sidekar.schema.model.ShortFieldModel;
import org.dellroad.sidekar.schema.model.StringFieldModel;

/**
 * Directory of the various Sidkear field types.
 *
 * <p>
 * Sidekar fields are represented at runtime by Java bean properties in their corresponding user entity classes.
 *
 * <p>
 * Each supported Sidekar field type has a corresponding Java type and a {@link org.dellroad.sidekar.annotation Sidekar annotation}
 * for the associated Java bean "getter" method. The corresponding "setter" methods are automatically associated
 * with the Sidekar field, except for the collections types which don't have "setter" methods.
 *
 * <p>
 * Note the primitive Sidekar field types like {@link FloatField} hold the corresponding Java primitive types like
 * <code>float</code>, not the "wrapper" types like <code>java.lang.Float</code>, for which there is no direct support.
 *
 * <p>
 * Subclasses of {@link SimpleField} are "nestable", i.e., they may appear as sub-fields of the collections types
 * like {@link SetField}, etc. For collections of primitive types, use the corresponding Java "wrapper" type for the
 * getter method return value, e.g., {@link java.util.Set Set&lt;Integer&gt;}; although the wrapper types are used
 * in the code, underneath primitive values are used and therefore {@code null} is neither allowed nor possible
 * as a value in such a collection.
 * Of course, {@code null} is a valid and supported value for all of the the non-primitive nestable types.
 *
 * <div style="margin-left: 20px;">
 * <table border="1" cellpadding="3" cellspacing="0">
 * <tr bgcolor="#ccffcc">
 *  <th align="left">Java Type</th>
 *  <th align="left">Sidekar Field</th>
 *  <th align="left">Annotation</th>
 *  <th align="left">Nestable</th>
 *  <th align="left"><a href="Index.html">Index</a> Sorting</th>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>boolean</code></td>
 *  <td><a href="BooleanField.html">BooleanField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td><code>false</code>, then <code>true</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>byte</code></td>
 *  <td><a href="ByteField.html">ByteField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>-128</code> to <code>127</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>char</code></td>
 *  <td><a href="CharacterField.html">CharacterField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>0</code> to <code>0xffff</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>short</code></td>
 *  <td><a href="ShortField.html">ShortField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>-32768</code> to <code>32767</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>int</code></td>
 *  <td><a href="IntegerField.html">IntegerField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>Integer.MIN_VALUE</code> to <code>Integer.MAX_VALUE</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>float</code></td>
 *  <td><a href="FloatField.html">FloatField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>Float.MIN_VALUE</code> to <code>Float.MAX_VALUE</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>long</code></td>
 *  <td><a href="LongField.html">LongField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>Long.MIN_VALUE</code> to <code>Long.MAX_VALUE</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td><code>double</code></td>
 *  <td><a href="DoubleField.html">DoubleField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in numerical order from <code>Double.MIN_VALUE</code> to <code>Double.MAX_VALUE</code></td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link String}</td>
 *  <td><a href="StringField.html">StringField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in lexical order, and then {@code null} is last</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>Any user model class instance ({@link Item})</td>
 *  <td><a href="ReferenceField.html">ReferenceField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>by entity in pre-order depth-first search of hierarchy, then by {@linkplain Item#getSidekarId id}</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>Array of any nestable type</td>
 *  <td><a href="ArrayField.html">ArrayField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>first by length, then lexically by elements</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link java.util.Date Date}</td>
 *  <td><a href="DateField.html">DateField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>in chronological order; {@code null} sorts last</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link java.math.BigInteger BigInteger}</td>
 *  <td><a href="BigIntegerField.html">BigIntegerField</a></td>
 *  <td><a href="annotation/SidekarField.html">SidekarField</a></td>
 *  <td>Yes</td>
 *  <td>{@code null} will be first, then in numerical order</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link java.util.Set Set&lt;E&gt;}, where {@code E} is a nestable type</td>
 *  <td><a href="SetField.html">SetField</a></td>
 *  <td><a href="annotation/SidekarSet.html">SidekarSet</a></td>
 *  <td>No</td>
 *  <td>N/A</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link java.util.List List&lt;E&gt;}, where {@code E} is a nestable type</td>
 *  <td><a href="ListField.html">ListField</a></td>
 *  <td><a href="annotation/SidekarList.html">SidekarList</a></td>
 *  <td>No</td>
 *  <td>N/A</td>
 * </tr>
 * <tr bgcolor="white" class="TableRowColor">
 *  <td>{@link java.util.Map Map&lt;K, V&gt;}, where {@code K} and {@code V} are nestable types</td>
 *  <td><a href="MapField.html">MapField</a></td>
 *  <td><a href="annotation/SidekarMap.html">SidekarMap</a></td>
 *  <td>No</td>
 *  <td>N/A</td>
 * </tr>
 * </table>
 * </div>
 *
 * <p>
 * The default value for all fields is the same as their Java defaults. For collections field types, which
 * are never {@code null}, the default is empty.
 */
public enum FieldType {

// CHECKSTYLE OFF: ParenPad

    // Primitive types
    BOOLEAN(        Boolean.TYPE,       BooleanField.class,     BooleanFieldModel.class),
    BYTE(           Byte.TYPE,          ByteField.class,        ByteFieldModel.class),
    CHARACTER(      Character.TYPE,     CharacterField.class,   CharacterFieldModel.class),
    SHORT(          Short.TYPE,         ShortField.class,       ShortFieldModel.class),
    INTEGER(        Integer.TYPE,       IntegerField.class,     IntegerFieldModel.class),
    FLOAT(          Float.TYPE,         FloatField.class,       FloatFieldModel.class),
    LONG(           Long.TYPE,          LongField.class,        LongFieldModel.class),
    DOUBLE(         Double.TYPE,        DoubleField.class,      DoubleFieldModel.class),

    // Non-primitive atomic types
    STRING(         String.class,       StringField.class,      StringFieldModel.class),
    DATE(           Date.class,         DateField.class,        DateFieldModel.class),
    BIG_INTEGER(    BigInteger.class,   BigIntegerField.class,  BigIntegerFieldModel.class),

    // Non-atomic simple types
    @SuppressWarnings("unchecked")
    ARRAY(          Object[].class,     (Class<? extends Field<?>>)ArrayField.class,
                                                                ArrayFieldModel.class),
    @SuppressWarnings("unchecked")
    REFERENCE(      Item.class,         (Class<? extends Field<?>>)ReferenceField.class,   ReferenceFieldModel.class),

    // Complex types
    @SuppressWarnings("unchecked")
    SET(            Set.class,          (Class<? extends Field<?>>)SetField.class,         SetFieldModel.class),
    @SuppressWarnings("unchecked")
    LIST(           List.class,         (Class<? extends Field<?>>)ListField.class,        ListFieldModel.class),
    @SuppressWarnings("unchecked")
    MAP(            Map.class,          (Class<? extends Field<?>>)MapField.class,         MapFieldModel.class);

// CHECKSTYLE ON: ParenPad

    private static final HashMap<Class<?>, FieldType> JAVA_TYPE_MAP = new HashMap<Class<?>, FieldType>();
    static {
        for (FieldType fieldType : values())
            JAVA_TYPE_MAP.put(fieldType.javaType, fieldType);
        for (FieldType fieldType : values()) {
            assert fieldType.isAtomic() == AtomicField.class.isAssignableFrom(fieldType.fieldType);
            assert fieldType.isSimple() == SimpleField.class.isAssignableFrom(fieldType.fieldType);
        }
    }

    private final Class<?> javaType;
    private final Class<? extends Field<?>> fieldType;
    private final Class<? extends FieldModel> modelType;

    private FieldType(Class<?> javaType, Class<? extends Field<?>> fieldType, Class<? extends FieldModel> modelType) {
        this.javaType = javaType;
        this.fieldType = fieldType;
        this.modelType = modelType;
    }

    public boolean isAtomic() {
        return ordinal() < ARRAY.ordinal();
    }

    public boolean isSimple() {
        return ordinal() < SET.ordinal();
    }

    /**
     * Get the {@link Field} type corresponding to this instance.
     *
     * @return corresponding {@link Field} subclass
     */
    public Class<? extends Field<?>> getFieldType() {
        return this.fieldType;
    }

    /**
     * Get the {@link FieldModel} type corresponding to this instance;
     *
     * @return the corresponding {@link FieldModel} subclass
     */
    public Class<? extends FieldModel> getFieldModelType() {
        return this.modelType;
    }

    @Override
    public String toString() {
        switch (this) {
        case ARRAY:
            return "array";
        case REFERENCE:
            return "reference";
        default:
            return this.javaType.getSimpleName();
        }
    }

    /**
     * Get the instance corresponding to the given Java class.
     *
     * @param javaType runtime type for field values (use {@link Item Item.class} for {@link #REFERENCE})
     * @return the corresponding {@link Field} class
     * @throws IllegalArgumentException if no matching field type exists
     */
    public static FieldType forJavaType(Class<?> javaType) {
        if (javaType.isArray())
            return ARRAY;
        FieldType fieldType = JAVA_TYPE_MAP.get(javaType);
        if (fieldType == null)
             throw new IllegalArgumentException("no field type for " + javaType);
        return fieldType;
    }
}

